/********************************************************************************
* Description: task manager
* Author: hsqiang
* Create Date: 2023-06-17
********************************************************************************/
#ifndef TASk_QUEUE_HPP_
#define TASK_QUEUE_HPP_

#include "channel.hpp"
#include "event_trace.hpp"
#include "assert_define.hpp"
#include <list>
#include <functional>
#include <chrono>
#include <algorithm>
#include <memory>
#include <sys/timerfd.h>
#include <unistd.h>
#include <error.h>

namespace event_framework
{
    using Timestamp = unsigned long long;   // millisecond
    class EventLoop;
    inline Timestamp GetNow()
    {
        using namespace std::chrono;
        auto now = steady_clock::now();
        auto duration = now.time_since_epoch();
        auto count = duration_cast<milliseconds>(duration).count();
        return count;
    }
    template <typename Functor>
    class TimerTask
    {
    public:
        TimerTask(Timestamp after_, Timestamp period_, Functor&& cmd_)
            : after(after_), period(period_), cmd(std::move(cmd_)), last(GetNow())
        {
            ASSERT(cmd);
        }
        ~TimerTask() = default;
        Timestamp GetExpiredTime()
        {
            if (after > 0) return after + last;
            return last + period;
        }
        bool IsExpired(Timestamp expired)
        {
            return GetExpiredTime() <= expired;
        }
        // call this method after first execute compeletly
        void Update()
        {
            last = GetNow();
            after = 0;
        }
        void Execute() { cmd(); Update(); }
    private:
        Timestamp after { 0 };
        Timestamp period { 0 };
        Timestamp last { 0 };   // last expired (or executed) time
        Functor cmd;
    };

    template <typename Functor>
    class TimerQueue final
    {
        using TimerTaskPtr = typename std::shared_ptr<TimerTask<Functor>>;
        using TimerTaskQue = std::list<TimerTaskPtr>;
    public:
        TimerQueue(EventLoop& loop_) : loop(loop_) {}
        ~TimerQueue() = default;
        bool Init()
        {
            int tmrfd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC);
            if (tmrfd < 0)
            {
                EVENT_TRACE_ERROR("[TimerQueue::Init] create timer fd failed with err=" << error);
                return false;
            }
            timerChannel = std::make_shared<Channel>(loop, tmrfd);
            ASSERT(timerChannel != nullptr);
            timerChannel->ConcernReadableEvent(std::bind(&TimerQueue::HandleTimeout, this));
            return true;
        }
        void Accept(Timestamp after, Timestamp period, Functor&& cmd)
        {
            if (period == 0)
            {
                AddTask(after, 0, std::move(cmd), onceQue);
                return;
            }
            AddTask(after, period, std::move(cmd), periodQue);
        }
    private:
        // execute in loop to avoid lock race with HandleTimeout()
        void AddTask(Timestamp after, Timestamp period, Functor&& cmd, TimerTaskQue& que)
        {
            auto newTask = std::make_shared<TimerTask<Functor>>(after, period, std::move(cmd));
            auto func = [this, &que, newTask] () -> void
            {
                auto it = std::find_if(que.begin(), que.end(),
                [newTask] (const TimerTaskPtr& task)
                {
                    return task->GetExpiredTime() >= newTask->GetExpiredTime();
                });
                if (it == que.end())
                {
                    que.emplace_back(std::move(newTask));
                    return;
                }
                que.emplace(it, std::move(newTask));
                UpdateExpirationSetting();
            };
            loop.Accept(func);
        }
        void HandleTimeout()
        {
            HandleTimerEvents();
            HandleOnceExpiration();
            HandlePeriodExpiration();
        }
        void HandleOnceExpiration()
        {
            auto it = onceQue.begin();
            for (; it != onceQue.end(); it++)
            {
                const auto& task = *it;
                ASSERT(task != nullptr);
                if (task->IsExpired(GetNow()))
                {
                    task->Execute();
                    continue;
                }
                break;
            }
            // no expired task
            if (it == onceQue.begin()) return;
            onceQue.erase(onceQue.begin(), it);
        }
        void HandlePeriodExpiration()
        {
            auto it = periodQue.begin();
            for (; it != periodQue.end(); it++)
            {
                const auto& task = *it;
                ASSERT(task != nullptr);
                if (task->IsExpired(GetNow()))
                {
                    task->Execute();
                    continue;
                }
                break;
            }
            // no expired task
            if (it == periodQue.begin()) return;
            periodQue.sort(
            [] (TimerTaskPtr t1, TimerTaskPtr t2)
            {
                return t1->GetExpiredTime() < t2->GetExpiredTime();
            });
        }
        Timestamp QueryEarliestExpiration()
        {
            auto it = onceQue.begin();
            // no once timer
            if (it == onceQue.end()) return 0;
            ASSERT(*it != nullptr);
            auto onceTime = (*it)->GetExpiredTime();

            it = periodQue.begin();
            // no period timer
            if (it == periodQue.end()) return 0;
            ASSERT(*it != nullptr);
            auto periodTime = (*it)->GetExpiredTime();
            return onceTime > periodTime ? periodTime : onceTime;
        }
        // expiration is millisecond
        void UpdateExpirationSetting()
        {
            auto expiration = QueryEarliestExpiration();
            struct itimerspec newValue;
            struct itimerspec oldValue;
            bzero(&newValue, sizeof newValue);
            bzero(&oldValue, sizeof oldValue);
            struct timespec ts;
            Timestamp duration = expiration - GetNow();
            ts.tv_sec = static_cast<decltype(ts.tv_sec)>(duration / 1000);
            ts.tv_nsec = static_cast<decltype(ts.tv_nsec)>((duration % 1000) * (1000 * 1000));
            newValue.it_value = ts;
            int ret = ::timerfd_settime(timerChannel->Fd(), 0, &newValue, &oldValue);
            if (ret < 0)
            {
                EVENT_TRACE_ERROR("[TimerQueue::UpdateExpirationSetting] timer update failed with err=" << error);
            }
        }
        void HandleTimerEvents()
        {
            unsigned long long howmany { 0 };
            ASSERT(timerChannel->Fd() > 0);
            ASSERT(timerChannel->Available());
            int num = read(timerChannel->Fd(), &howmany, sizeof howmany);
            if (num != howmany)
            {
                EVENT_TRACE_ERROR("[TimerQueue::HandleTimerEvents] read timer failed with err=" << error);
            }
        }
        TimerTaskQue onceQue;        // sorted by expired time ascend
        TimerTaskQue periodQue;      // sorted by next expired time ascend
        ChannelPtr timerChannel;
        EventLoop& loop;
    };
}

#endif